home *** CD-ROM | disk | FTP | other *** search
/ User's Choice Windows CD / User's Choice Windows CD (CMS Software)(1993).iso / windows4 / pcproj.zip / ACTIVITY.CLS < prev    next >
Text File  |  1988-11-30  |  11KB  |  419 lines

  1. /* Activity is a formal class to group common behavior among
  2.    tasks and milestones for projects.  Activities know how to
  3.    determine if they are on the critical path and keep track
  4.    of their earlyStart, lateFinish and slack time.
  5.    The user can set an earlyStart or lateFinish to override
  6.    what is calculated.
  7.  
  8.    Since Activity descends from Node, it inherits all of the
  9.    methods and instance variables from Node, e.g. name, desc,
  10.    inputs, outputs, etc.
  11. */!!
  12.  
  13. inherit(Node, #Activity, #(earlyStart     /* calculated */ 
  14. lateFinish     /* calculated */
  15. userEarlyStart /* user entered */
  16. userLateFinish /* user entered */
  17. slack          /* slack time */
  18. critical       /* boolean */), 2, nil)!!
  19.  
  20. now(ActivityClass)!!
  21.  
  22. now(Activity)!!
  23.  
  24. /* Return the class name.  Generic version.
  25.    Ancestors will redefine this to use resources. */
  26. Def className(self)
  27. {
  28.   ^asString(class(self));
  29. }!!
  30.  
  31. /* Return true if the activity is "complete", e.g.
  32.    connected.  Descendants may redefine this.
  33.    This is for experimental use only.   */
  34. Def  complete(self)
  35.   ^size(inputs) + size(outputs) > 0;
  36. }!!
  37.  
  38. /* In general, activities place no limit on the number
  39.    of connections.  Descendants, such as Task, will
  40.    redefine this to place limits. */
  41. Def  maxConnectionNames(self, names)
  42.   ^names;
  43. }!!
  44.  
  45. /* Delete an activity.  First check to see if it is
  46.    connected to anything and check with the user. */
  47. Def  delete(self)
  48.   if size(inputs) + size(outputs) = 0
  49.     cor yesNoBox(loadString(PW_WARNING),
  50.                  name + loadString(PW_ACTUSE1) +
  51.                  loadString(PW_ACTUSE2) + CR_LF +
  52.                  loadString(PW_DELETE)) == IDYES
  53.      do(inputs,
  54.        {using(input)
  55.         disconnect(input, self);
  56.      });
  57.      do(outputs,
  58.        {using(output)
  59.         disconnect(self, output);
  60.      });
  61.      removeNode(network, self);
  62.      remove(network.display, pos(self));
  63.   endif;      
  64. }!!
  65.  
  66. /* Parse a string of nodeNames and return a set of
  67.    nodes.  Checks to see if they exist.  */
  68. Def  parseNodeNames(self, nodeNames | names, nodes, aNode)
  69. {
  70.   names := words(nodeNames);
  71.   names := maxConnectionNames(self, names);
  72.   nodes := new(OrderedCollection, 5);
  73.   do(names,
  74.     {using(name) 
  75.      if aNode := checkNodeExists(network, name)
  76.         add(nodes, aNode);
  77.      endif;
  78.   });
  79.   ^nodes;
  80. }!!
  81.  
  82. /* Checks the connections and reconnects if necessary. 
  83.    If the connections change, we may have to recalc
  84.    the entire project. */
  85. Def  checkConnection(self, inputsString, outputsString
  86.                      | newInputs, newOutputs, calcReqd)
  87. {  
  88.   if inputsString <> getInputNames(self)
  89.     newInputs := parseNodeNames(self, inputsString);
  90.     do(inputs,
  91.       {using(input)
  92.        if not(input in newInputs)
  93.           disconnect(network, input.name, self.name); 
  94.           calcReqd := true;
  95.        endif;
  96.      });
  97.     do(newInputs,
  98.       {using(input)
  99.        if not(input in inputs)
  100.         connect(network, input.name, self.name); 
  101.         calcReqd := true;
  102.        endif;
  103.      });
  104.    endif;
  105.  
  106.   if outputsString <> getOutputNames(self)
  107.     newOutputs := parseNodeNames(self, outputsString);   
  108.     do(outputs,
  109.       {using(output)
  110.        if not(output in newOutputs)
  111.          disconnect(network, self.name, output.name); 
  112.          calcReqd := true;
  113.        endif
  114.      });
  115.     do(newOutputs,
  116.       {using(output)
  117.        if not(output in outputs)
  118.          connect(network, self.name, output.name); 
  119.          calcReqd := true;
  120.        endif;
  121.      });
  122.   endif;            
  123.   
  124.   if autoCalc(network) cand calcReqd
  125.     recalc(network)
  126.   endif;
  127. }!!
  128.  
  129. /* Return a string of output names. 
  130.    This is useful when editing. */
  131. Def  getOutputNames(self | str)
  132. {
  133.   str := "";
  134.   do(outputs,
  135.     {using(output) str := str + getName(output) + " ";
  136.   });
  137.   ^str;
  138. }!!
  139.  
  140. /* Return a string of input names. 
  141.    This is useful when editing. */
  142. Def  getInputNames(self | str)
  143. {
  144.   str := "";
  145.   do(inputs,
  146.     {using(input) str := str + getName(input) + " ";
  147.   });
  148.   ^str;
  149. }!!
  150.  
  151. /* Display and edit the activity information. 
  152.    Descendants that use this should have a dialogClass()
  153.    method that returns the dialog class to be used. The
  154.    dialog class should define methods run() and setEditItem().
  155. */
  156. Def  editInfo(self | dlg, retValue)
  157. {
  158.   showWaitCurs();              /* this can take a while */
  159.   dlg := new(dialogClass(self));
  160.   setEditItem(dlg, self);
  161.   retValue := run(dlg, ThePort);
  162.   showOldCurs();               /* all done */
  163.   ^retValue;
  164. }!!
  165.  
  166. /* In the simplest case an activity is critical if slack = 0.
  167.    However, if the user has set the lateFinish, it could
  168.    introduce excess slack to the project.  Therefore, a node
  169.    is  critical if its slack is equal to the excess slack. */
  170. Def  calcCritical(self)
  171.   critical := (slack = getSlack(network));
  172. }!!
  173.  
  174. /* Set the values of an activity. 
  175.    Values is an array of name, desc,
  176.    userEarlyStart, userLateFinish. */
  177. Def  setValues(self, values | oldUES, oldULF)
  178. {
  179.   oldUES := userEarlyStart;
  180.   oldULF := userLateFinish;
  181.   name := values[0];
  182.   desc := values[1];
  183.   userEarlyStart := values[2];
  184.   userLateFinish := values[3];
  185.   if autoCalc(network) cand 
  186.      (oldUES <> userEarlyStart cor
  187.       oldULF <> userLateFinish)
  188.     recalc(self);
  189.   endif;
  190. }!!
  191.  
  192. /* Get the user set earlyStart time. */
  193. Def  getUserEarlyStart(self)
  194.  ^userEarlyStart;
  195. }!!
  196.  
  197. /* Get the user set lateFinish time. */
  198. Def  getUserLateFinish(self)
  199.  ^userLateFinish;
  200. }!!
  201.  
  202. /* Return a string to be used as a caption in a window. */
  203. Def  makeCaption(self)
  204. {
  205.   ^className(self) + ": "+getName(self)+
  206.    if critical(self)
  207.       loadString(PW_CRITICAL)
  208.    else
  209.       loadString(PW_NONCRITICAL)
  210.    endif;
  211. }!!
  212.  
  213. /* Summarize useful information on a single line. */
  214. Def  getInfoLine(self | str)
  215. {
  216.   if critical(self)
  217.     str := "*";
  218.   else
  219.     str := " ";
  220.   endif;
  221.   ^str + field(name, 8) 
  222.    + field(className(self), 4) + " "
  223.    + field(asString(getCost(self)), 3) + " "
  224.    + field(asString(getTime(self)), 2) + " " 
  225.    + field(asString(getSlack(self)), 3) + " " 
  226.    + field(asString(getEarlyStart(self)), 8) + " "
  227.    + field(asString(getLateFinish(self)), 8);
  228. }!!
  229.  
  230. /* Get the lateFinish time.  At End, lateFinish = earlyStart,
  231.    unless the user set a lateFinish. */
  232. Def  getLateFinish(self)
  233.   if (size(outputs) == 0 and not(userLateFinish))
  234.     lateFinish := earlyStart
  235.   endif;
  236.  ^lateFinish;
  237. }!!
  238.  
  239. /* Calculate slack for an Activity.  This is done
  240.    on the backward recalc pass when ES, LF are
  241.    up to date.  Slack is never < 0. */
  242. Def  calcSlack(self)
  243. {  
  244.   ^slack := max(0, getLateStart(self) - getEarlyStart(self));
  245. }!!
  246.  
  247. /* Get the slack value.  */
  248. Def getSlack(self)
  249. {
  250.   ^slack;
  251. }!!
  252.  
  253. /* Return the status of whether the node is critical or not. */
  254. Def  critical(self)
  255.   ^critical;
  256. }!!
  257.  
  258. /* Invalidate a node and recursively invalidate all
  259.    nodes it's connected to.  This is used to force
  260.    an entire recalc of the network. */
  261. Def invalidate(self, visited)
  262. {
  263.  reset(self);                      /* reset ivars */
  264.  do(outputs,                       /* pass along */
  265.     {using(elem)
  266.      if not(elem in visited)       /* avoid looping */
  267.         add(visited, elem);
  268.         invalidate(elem, visited); /* recurse */
  269.      endif;
  270.   });
  271. }!!
  272.  
  273. /* Recalculate the network from this node onwards.
  274.    This requires forcing a forward recalc1 and a
  275.    backwards recalc2 from the end of the net. */
  276. Def  recalc(self)
  277. {
  278.   recalc1(self,true);      /* force forward recalc1 */
  279.   recalc2(network);        /* do backwards recalc2 */
  280. }!!
  281.  
  282. /* Backward recalc pass:  (not optimized)
  283.    Recalculate an activity's lateFinish then its slack and
  284.    determine if it's critical.
  285.    If the user has set a lateFinish, use it instead of
  286.    recalculating.  Always propogate recalc2 since a
  287.    change to the time of a node will not change lateFinish,
  288.    but it can change slack and critical, which are only
  289.    known on the backwards pass.
  290.    
  291.    formula:  LF = min(LF(i) - time(i)) for all outputs i
  292.    
  293.    Note: This could be optimized to only propogate recalc2
  294.    as far back as the next Milestone.  */
  295. Def  recalc2(self)
  296. {  
  297.   if (userLateFinish)
  298.     lateFinish := userLateFinish;   /* user override */
  299.   else
  300.     lateFinish := asLong(date(12,31,1999));
  301.     do(outputs,
  302.       {using(output)
  303.       lateFinish := min(lateFinish, 
  304.                          getLateFinish(output) - getTime(output));
  305.     });
  306.     lateFinish := asDate(lateFinish);
  307.   endif;
  308.   
  309.   calcSlack(self);
  310.   calcCritical(self);
  311.  
  312.   /* Continue sending the recalc2 message. */
  313.  
  314.   do(inputs,
  315.     {using(input)
  316.     recalc2(input);
  317.   });
  318. }!!
  319.  
  320. /* Forward recalc pass: (optimized minimal recalc)
  321.    Recalculate an activity's earlyStart.  If the user has
  322.    set an earlyStart, use it instead of recalculating.
  323.    Send a message to the node's outputs to recalculate only
  324.    if a forced recalc is required, or if earlyStart changes.
  325.    
  326.    formula:  ES = max(ES(i) + time(i)) for all inputs i
  327.  
  328.    arguments:
  329.      timeChanged: force a recalc1 if the time has changed
  330. */
  331. Def  recalc1(self, timeChanged | oldEarlyStart)
  332. {   
  333.   oldEarlyStart := earlyStart;
  334.  
  335.   if (userEarlyStart)
  336.     earlyStart := userEarlyStart;     /* user override */
  337.   else
  338.     earlyStart := asLong(new(Date));    
  339.     do(inputs,
  340.       {using(input) 
  341.       earlyStart := max(earlyStart,
  342.                      getEarlyStart(input) + getTime(input));                 
  343.     });
  344.     earlyStart := asDate(earlyStart);
  345.   endif;
  346.   
  347.   /* Recalculate outputs only if earlyStart changed OR if time has
  348.      changed.  Don't force it to continue beyond the next level.  */
  349.  
  350.   if timeChanged cor (earlyStart <> oldEarlyStart)   
  351.     do(outputs,
  352.       {using(output) recalc1(output, nil);
  353.     });
  354.   endif;
  355. }!!
  356.  
  357. /* Initialize a new Activity. */
  358. Def  init(self)
  359. { init(self:Node);        /* use ancestor init */
  360.   reset(self);            /* reset ivars */
  361. } !!
  362.  
  363. /* Reset the instance variables. This is called by
  364.    init and when an entire recalc is forced. */
  365. Def reset(self)
  366. {
  367.   slack := 0;
  368.   lateFinish := earlyStart := new(Date);
  369.   critical := nil;
  370. }!!
  371.  
  372. /* The user can enter a late finish date to be used
  373.    instead of the calculated value. */
  374. Def  setLateFinish(self, aDate)
  375.   userLateFinish := aDate;
  376.   if autoCalc(network)        /* just do backwards calc */
  377.      recalc2(network);
  378.   endif;
  379. }!!
  380.  
  381. /* The user can enter an early start date to be used
  382.    instead of the calculated value. */
  383. Def  setEarlyStart(self, aDate)
  384.   userEarlyStart := aDate;
  385.   if autoCalc(network)
  386.     recalc(self);
  387.   endif;
  388. } !!
  389.  
  390. /* Get the earlyStart time. */
  391. Def  getEarlyStart(self)
  392.  ^earlyStart;
  393. } !!
  394.  
  395. /* Calculcate the earlyFinish time. */
  396. Def  getEarlyFinish(self)
  397.  ^asDate(getEarlyStart(self) + getTime(self));
  398. }!!
  399.  
  400. /* Calcualate the lateStart time. */
  401. Def  getLateStart(self)
  402.  ^asDate(getLateFinish(self) - getTime(self));
  403. }!!
  404.  
  405.  
  406.